using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using InfiniTec.Data;
using InfiniTec.DirectoryServices.Properties;
using InfiniTec.Threading;
using AsyncOperation=InfiniTec.Threading.AsyncOperation;

namespace InfiniTec.DirectoryServices
{
    /// <summary>
    /// A base class containing convinience methods for accessing ActiveDirectory
    /// entries.
    /// </summary>
    public abstract class ActiveDirectoryEntry
    {
        private readonly AsyncOperationTracker _OperationTracker = new AsyncOperationTracker();
        private readonly PropertyDefinitionCollection _RequiredFields;

        /// <summary>
        /// Initializes a new instance of the <see cref="ActiveDirectoryEntry"/> class.
        /// </summary>
        protected ActiveDirectoryEntry()
        {
            _RequiredFields = new PropertyDefinitionCollection();
            AddRequiredFields(SchemaMapping.CommonSchema);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ActiveDirectoryEntry"/> class.
        /// </summary>
        /// <param name="objectId">The object id.</param>
        /// <param name="connection">The connection.</param>
        protected ActiveDirectoryEntry(Guid objectId, Connection connection) : this()
        {
            BaseItem = new Item(string.Format("<Guid={0}>", objectId), connection);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ActiveDirectoryEntry"/> class.
        /// </summary>
        /// <param name="baseItem">The base item.</param>
        protected ActiveDirectoryEntry(Item baseItem) : this()
        {
            BaseItem = baseItem;
        }

        /// <summary>
        /// Gets the operation tracker used for this instnace.
        /// </summary>
        /// <value>The operation tracker.</value>
        protected AsyncOperationTracker OperationTracker
        {
            get { return _OperationTracker; }
        }

        /// <summary>
        /// Gets a read-only list of properties which are required for the proper functioning of this instance.
        /// </summary>
        /// <value>The required fields.</value>
        protected IList<IPropertyDefinition> RequiredFields
        {
            get { return _RequiredFields; }
        }

        /// <summary>
        /// Gets a list containing the properties to load during a refresh operation.
        /// If left empty, all properties are loaded.
        /// </summary>
        /// <value>The properties to load.</value>
        public IList<IPropertyDefinition> PropertiesToLoad
        {
            get { return BaseItem.PropertiesToLoad; }
        }

        /// <summary>
        /// Provides access to the properties of the base item.
        /// </summary>
        /// <value>The properties.</value>
        public PropertyCollection Properties
        {
            [DebuggerStepThrough]
            get { return (BaseItem != null) ? BaseItem.Properties : null; }
        }

        /// <summary>
        /// Gets the base item.
        /// </summary>
        /// <value>The base item.</value>
        protected Item BaseItem { get; set; }

        /// <summary>
        /// Gets the distinguished name.
        /// </summary>
        /// <value>The distinguished name.</value>
        public string DistinguishedName
        {
            get { return Properties.GetPropertyValue(WellknownProperties.Common.DistinguishedName); }
        }

        /// <summary>
        /// Gets the canonicalname.
        /// </summary>
        /// <value>The canonical name.</value>
        public string CanonicalName
        {
            get { return Properties.GetPropertyValue(WellknownProperties.Common.CanonicalName); }
        }

        /// <summary>
        /// Gets or sets the relative name of the item.
        /// </summary>
        /// <value>The relative name of the item.</value>
        public string RelativeName
        {
            get { return Properties.GetPropertyValue(WellknownProperties.Common.RelativeName); }
            set { Properties.SetPropertyValue(WellknownProperties.Common.RelativeName, value); }
        }

        /// <summary>
        /// Gets or sets the displayname of the item.
        /// </summary>
        /// <value>The displayname of the item.</value>
        public string DisplayName
        {
            get { return Properties.GetPropertyValue(WellknownProperties.Common.DisplayName); }
            set { Properties.SetPropertyValue(WellknownProperties.Common.DisplayName, value); }
        }

        /// <summary>
        /// Gets the object category.
        /// </summary>
        /// <value>The object category.</value>
        public string ObjectCategory
        {
            get { return Properties.GetPropertyValue(WellknownProperties.Common.ObjectCategory); }
        }

        /// <summary>
        /// Gets the object classes.
        /// </summary>
        /// <value>The object class.</value>
        public IList<string> ObjectClass
        {
            get { return Properties.GetMultiValuedPropertyValue(WellknownProperties.Common.ObjectClass); }
        }

        /// <summary>
        /// Gets the object GUID.
        /// </summary>
        /// <value>The object GUID.</value>
        public Guid ObjectGuid
        {
            get { return Properties.GetPropertyValue(WellknownProperties.Common.ObjectGuid); }
        }

        /// <summary>
        /// This event is triggered, when a refresh operation completes.
        /// </summary>
        public event AsyncCompletedEventHandler RefreshCompleted;

        /// <summary>
        /// This event is triggered, when a Save operation completes.
        /// </summary>
        public event AsyncCompletedEventHandler SaveCompleted
        {
            add { BaseItem.SaveCompleted += value; }
            remove { BaseItem.SaveCompleted -= value; }
        }

        /// <summary>
        /// Adds the required fields which are needed to access the various properties of the
        /// object.
        /// </summary>
        /// <param name="fields">The required fields.</param>
        protected void AddRequiredFields(IPropertyDefinition[] fields)
        {
            _RequiredFields.AddRange(fields);
        }

        /// <summary>
        /// This method is called before the <see cref="Refresh"/> or <see cref="RefreshAsync"/>
        /// operation is called.
        /// </summary>
        protected virtual void PreRefresh()
        {
            if (BaseItem == null)
            {
                return;
            }

            foreach (IPropertyDefinition property in _RequiredFields)
            {
                if (!BaseItem.PropertiesToLoad.Contains(property))
                    BaseItem.PropertiesToLoad.Add(property);
            }
        }

        /// <summary>
        /// This method is called after the <see cref="Refresh"/> operation has completed.
        /// </summary>
        public virtual void PostRefresh()
        {
        }

        /// <summary>
        /// This method is called before the <see cref="Save"/> or <see cref="SaveAsync"/>
        /// operation is called.
        /// </summary>
        protected virtual void PreSave()
        {
        }

        /// <summary>
        /// This method is called after an async refresh operation has completed.
        /// </summary>
        /// <param name="e">An <see cref="AsyncCompletedEventArgs"/> instance indicating the success
        /// or failure of the refresh operation.</param>
        protected virtual void OnRefreshCompleted(AsyncCompletedEventArgs e)
        {
            if (RefreshCompleted != null) RefreshCompleted(this, e);
        }

        /// <summary>
        /// Synchronously refreshes the properties of the curren item.
        /// </summary>
        public void Refresh()
        {
            AsyncOperation operation = GetRefreshOperation();
            operation.RunSynchronously();
        }

        /// <summary>
        /// Asynchronously refreshes the properties of this instance.
        /// </summary>
        public void RefreshAsync()
        {
            AsyncOperation operation = GetRefreshOperation();

            operation.Completed += RefreshCompleted;

            operation.Run();
        }

        /// <summary>
        /// Gets the async operation operation used to refresh the item.
        /// </summary>
        /// <returns>An async operation which can be used to refresh the item.</returns>
        public abstract AsyncOperation GetRefreshOperation();

        /// <summary>
        /// Synchronously saves the properties of the curren item.
        /// <param name="lazyCommit">true, if the save operation should return before the changes have been committed to disk. False, otherwise</param>
        /// </summary>
        public void Save(bool lazyCommit)
        {
            if (BaseItem == null)
                throw new InvalidOperationException(Resources.NotInitializedException_NotBoundToBaseItem);

            PreSave();

            BaseItem.Save(lazyCommit);
        }

        /// <summary>
        /// Asynchronously saves the properties of the curren item.
        /// <param name="lazyCommit">true, if the save operation should return before the changes have been committed to disk. False, otherwise</param>
        /// </summary>
        public void SaveAsync(bool lazyCommit)
        {
            if (BaseItem == null)
                throw new InvalidOperationException(Resources.NotInitializedException_NotBoundToBaseItem);

            PreSave();

            BaseItem.SaveAsync(lazyCommit);
        }

        /// <summary>
        /// Synchronously deletes the properties of the curren item.
        /// </summary>
        public void Delete()
        {
            throw new NotImplementedException();
//            BaseItem.Delete();
        }

        /// <summary>
        /// Asynchronously deletes the properties of the curren item.
        /// </summary>
        public void DeleteAsync()
        {
            throw new NotImplementedException();
//            BaseItem.DeleteAsync();
        }

        /// <summary>
        /// Returns wheter the specified flags are set in the given value.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <param name="flags">The flags to check</param>
        /// <returns>Returns true, if the flags are set in the specified value. False otherwise. If 
        /// the specified value is null, null is returned.</returns>
        protected static bool? CheckFlagValue(int? value, int flags)
        {
            if (value == null) return null;

            return ((value.Value & flags) == flags);
        }

        /// <summary>
        /// Gets the schema entry for the current item.
        /// </summary>
        /// <returns></returns>
        public Item GetSchemaEntry()
        {
            return new Item(ObjectCategory, BaseItem.Connection);
        }

        /// <summary>
        /// Gets an <see cref="Threading.AsyncOperation"/> which returns the configuration naming context.
        /// </summary>
        /// <param name="connection">The connection.</param>
        /// <param name="userState">A user-defined object that is passed to the method invoked when the asynchronous operation completes.</param>
        /// <returns></returns>
        public static AsyncOperation<Item> GetConfigurationNamingContext(Connection connection, object userState)
        {
            return new AsyncOperation<Item>(GetConfigurationNamingContextInternal(connection), userState);
        }

        private static IEnumerable<OperationAction> GetConfigurationNamingContextInternal(Connection connection)
        {
            var rootDse = new Item(WellknownDistinguishedNames.RootDse, connection);

            yield return new NestedOperation(rootDse.GetRefreshOperation());

            string configurationNC = rootDse.Properties.GetProperty(WellknownProperties.RootDse.ConfigurationContext).Value;

            yield return new OperationResult<Item>(new Item(configurationNC, connection));
        }
    }
}